// ts加密解密
import {JSEncrypt} from "jsencrypt"

// 密钥对生成 http://web.chacuo.net/netrsakeypair

export const publicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtrn7rlokbWDSQtsAh6K3sr5eT3oBsTizrUGS6P9rqX8KEpER9vjDUoNVgQVo9iAQLM7WtMayxU5KL3YDskBuCcZKPhFZ7dUjPdhKaoEHMs5dTUaLjFvlnDEmRZNkY3ufcUIyBbFtTd0XN0a8qYJYz/E75xbcuFpanJ/j5e0oN0GOhQ4eoz3rWVHaSs9qLdzLCUqZqptIAp79Q5ru4Gg5NsOOQkOpzkbW6uMT68JurKK6+GDg59C8V9EnC9YpG4xvx+hUYlnO8yUHqHd4R30Cw5VwKGsSypeGGaaUFmcYqj8DYnVbWRd3wxwVdIiBiasWmu7w9CpL68v/TdzI3bpAVwIDAQAB'

/**
 * 混合加密工具 - 使用RSA加密AES密钥，AES加密数据
 */
export class HybridEncryptHelper {
    private rsaEncrypt: JSEncrypt;
    private aesKey: string | null = null;
    private aesKeyBytes: Uint8Array | null = null;

    constructor() {
        this.rsaEncrypt = new JSEncrypt();
    }

    /**
     * 设置RSA公钥
     * @param publicKey - PEM格式的RSA公钥
     */
    setRsaPublicKey(publicKey: string): void {
        this.rsaEncrypt.setPublicKey(publicKey);
    }

    /**
     * 生成原始 AES 密钥（不进行 Base64 编码）
     * @param length 密钥长度，可选 128、192 或 256 位，默认为 256 位
     * @returns Uint8Array 类型的原始密钥字节
     */
    generateAesKey(length: 128 | 192 | 256 = 256): Uint8Array {
        const byteLength = length / 8;
        const keyBytes = new Uint8Array(byteLength);
        window.crypto.getRandomValues(keyBytes);

        this.aesKeyBytes = keyBytes; // 保存原始字节
        this.aesKey = String.fromCharCode(...keyBytes);
        return keyBytes; // 直接返回原始字节数组
    }

    /**
     * 使用AES-CBC加密数据
     * @param data - 待加密的字符串
     * @returns 加密后的Base64字符串（格式：IV:密文）
     */
    async encryptDataToBase64(data: string): Promise<string> {
        if (!this.aesKeyBytes) {
            throw new Error('AES密钥未初始化，请先调用generateAesKey');
        }

        // 导入AES密钥
        const aesKey = await this.importAesKey(this.aesKeyBytes);

        // 生成随机IV
        const iv = window.crypto.getRandomValues(new Uint8Array(16));

        // 加密数据
        const encoder = new TextEncoder();
        const dataBuffer = encoder.encode(data);
        const encryptedBuffer = await window.crypto.subtle.encrypt(
            {name: 'AES-CBC', iv},
            aesKey,
            dataBuffer
        );

        // 拼接IV和密文并转换为Base64
        const encryptedBytes = new Uint8Array(encryptedBuffer);
        const combined = new Uint8Array(iv.length + encryptedBytes.length);
        combined.set(iv);
        combined.set(encryptedBytes, iv.length);

        return this.uint8ArrayToBase64(combined);
    }

    /**
     * 加密表单数据
     * @param formData 表单数据对象
     * @param aesKey AES密钥
     * @returns 加密后的表单数据
     */
    public async encryptFormDataToBase64AES<T extends object>(
        formData: T & { aesSecretKey?: string }
    ): Promise<T & { aesSecretKey?: string }> {
        const encryptedData = {...formData};
        const formKeys = Object.keys(encryptedData) as (keyof typeof encryptedData)[];

        for (const key of formKeys) {
            // 只处理字符串类型的值
            const value = encryptedData[key];
            if (typeof value === 'string') {
                encryptedData[key] = await this.encryptDataToBase64(value) as any;
            }
        }

        return encryptedData;
    }

    /**
     * 使用RSA加密AES密钥
     * @returns 加密后的Base64编码字符串
     */
    encryptAesKeyToBase64(): string {
        if (!this.aesKey) {
            throw new Error('AES密钥未初始化，请先调用generateAesKey');
        }

        const encrypted = this.rsaEncrypt.encrypt(this.aesKey);
        if (!encrypted) {
            throw new Error('RSA加密失败，请检查公钥是否正确');
        }

        return btoa(encrypted);
    }

    /**
     * 导入AES密钥
     */
    private async importAesKey(keyBytes: Uint8Array): Promise<CryptoKey> {
        return window.crypto.subtle.importKey(
            'raw',
            keyBytes,
            {name: 'AES-CBC'},
            false,
            ['encrypt']
        );
    }

    /**
     * Uint8Array转Base64
     */
    private uint8ArrayToBase64(array: Uint8Array): string {
        return btoa(String.fromCharCode(...array));
    }
}
